//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <crust.h>

//
//  Driver
//
extern"C" IDeviceDriver *  PoRegisterDevice(wchar_t * Name, IInterface * pObject);
PInterface CDeviceDriver::Probe(
    /* [in]  */ REIID riid)
{
    if (riid == EIID_IInterface) {
        return (IInterface *)(IDeviceDriver *)this;
    }
    else if (riid == EIID_IDeviceDriver) {
        return (IDeviceDriver *)this;
    }
    else if (riid == EIID_CLASS_INFO) {
        return (IInterface *)&ECLSID_CDeviceDriver;
    }
    else if (riid == EIID_INTERFACE_INFO) {
        return (IInterface *)&EIID_IDeviceDriver;
    }

    return NULL;
}

UInt32 CDeviceDriver::AddRef(void)
{
    Int32 lRef = m_cRef.Increment();
    return (UInt32)lRef;
}

UInt32 CDeviceDriver::Release(void)
{
    Int32 lRef = m_cRef.Decrement();
    if (0 == lRef) {
        Dispose();
    }
    return (UInt32)lRef;
}

ECode CDeviceDriver::Aggregate(
    /* [in] */ AggrType aggrType,
    /* [in] */ PInterface pObject)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDeviceDriver::GetDomain(
    /* [out] */ PInterface *ppObj)
{
    return E_NOT_IMPLEMENTED;
}

//
// Device
//
class Device : public DLinkNode
{
public:
    Device(const wchar_t *wszDeviceName, uint_t uDeviceNo, void *pvParameter);

public:
    wchar_t m_wszDeviceName[c_uMaxLengthOfDeviceName + 1];
    uint_t  m_uDeviceNo;
    void    *m_pvParameter;
    IStub   *m_pDriverStub;
};

INLINE Device::Device(
    const wchar_t *wszDeviceName, uint_t uDeviceNo, void *pvParameter)
{
    assert(wszDeviceName);

    wcsncpy(m_wszDeviceName, wszDeviceName, c_uMaxLengthOfDeviceName);
    m_wszDeviceName[c_uMaxLengthOfDeviceName] = L'\0';

    m_uDeviceNo     = uDeviceNo;
    m_pvParameter   = pvParameter;
    m_pDriverStub   = NULL;
}

//
// local variables
//
// TODO: optimize
STATIC DLinkNode    s_deviceListHead;
#ifdef DEBUG_KMUTEX
STATIC KMutex       s_deviceListLock(__FILE__, __LINE__);
#else 
KMutex       s_deviceListLock;
#endif //DEBUG_KMUTEX
//
// local function declarations
//
STATIC const DriverConfig *FindDriverConfig(const wchar_t *wszDeviceName);

STATIC Device *FindDevice(const wchar_t *wszDeviceName, uint_t uDeviceNo);

STATIC void UIntToWStr(
        wchar_t wszBuffer[c_uMaxLengthOfUIntString + 1], uint_t uValue);

//
// global function definitions
//
EXTERN DECL_CODEINIT bool_t CDECL InitDrivers()
{
    s_deviceListHead.Initialize();

    bool_t bResult = TRUE;
    for (uint_t i = 0; i < dzcfg.uNumberOfDeviceConfigs; i++) {
        ECode ec = DzRegisterDevice(dzcfg.pDeviceConfigs[i].wszDeviceName,
            dzcfg.pDeviceConfigs[i].uDeviceNo,
            dzcfg.pDeviceConfigs[i].pvParameter);
        if (FAILED(ec)) bResult = FALSE;
    }

    return bResult;
}

EXTERN ECode CDECL DzRegisterDevice(
    const wchar_t *wszDeviceName, uint_t uDeviceNo, void *pvParameter)
{
    assert(wszDeviceName);

    if (c_uMaxLengthOfDeviceName < wcslen(wszDeviceName)) {
        return E_DEVICE_NAME_TOO_LONG;
    }

    ECode ec = NOERROR;
    Device *pDevice;
    const DriverConfig *pDriverConfig;
    IDeviceDriver *pDriver;
    wchar_t wszDriverServiceName[c_uMaxLengthOfDriverServiceName + 1];

    s_deviceListLock.Lock();

    if (NULL != FindDevice(wszDeviceName, uDeviceNo)) {
        ec = E_DEVICE_EXISTS;
        goto Exit;
    }

    pDevice = new Device(wszDeviceName, uDeviceNo, pvParameter);
    if (NULL == pDevice) {
        ec = E_OUT_OF_MEMORY;
        goto Exit;
    }

    s_deviceListHead.InsertFirst(pDevice);

    pDriverConfig = FindDriverConfig(wszDeviceName);
    if (NULL == pDriverConfig) {
        ec = ELADRV_S_DRIVER_NOT_FOUND;
        goto Exit;
    }

    assert(pDriverConfig->routine);
    pDriver = (*pDriverConfig->routine)(uDeviceNo, pvParameter);
    if (NULL == pDriver) {
        ec = ELADRV_S_CREATE_DRIVER_FAILED;
        goto Exit;
    }

    BuildDriverServiceName(wszDriverServiceName, wszDeviceName, uDeviceNo);
#if defined(_techfaith) || defined(_gingko) || defined( _neptune)
    pDriver=PoRegisterDevice(wszDriverServiceName,pDriver);//temp solution
#elif defined(_c28) || defined(_t8)
    NotifyByIsr(EVENT_REG_DEVICE, (int)wszDriverServiceName, &pDriver);
#endif
    IStub *pIStub;
    ec = DzRegisterService(wszDriverServiceName, (IDeviceDriver *)pDriver, &pIStub);
    if (FAILED(ec)) {
        kprintf("*WARNING* Failed to register: %S\n", wszDriverServiceName);
        pDriver->Release();
        ec = ELADRV_S_REGISTER_DRIVER_FAILED;
        goto Exit;
    }

    pDriver->Release();
    pDevice->m_pDriverStub = pIStub;

Exit:
    s_deviceListLock.Unlock();
    return ec;
}

EXTERN ECode CDECL DzUnregisterDevice(
    const wchar_t *wszDeviceName, uint_t uDeviceNo)
{
    assert(wszDeviceName);

    if (c_uMaxLengthOfDeviceName < wcslen(wszDeviceName)) {
        return E_DEVICE_NAME_TOO_LONG;
    }

    ECode ec = NOERROR;
    Device *pDevice;

    s_deviceListLock.Lock();

    pDevice = FindDevice(wszDeviceName, uDeviceNo);
    if (NULL == pDevice) {
        s_deviceListLock.Unlock();
        return E_DEVICE_NOT_FOUND;
    }

    pDevice->Detach();

    s_deviceListLock.Unlock();

    if (NULL != pDevice->m_pDriverStub) {
        wchar_t wszDriverServiceName[c_uMaxLengthOfDriverServiceName + 1];
        BuildDriverServiceName(wszDriverServiceName, wszDeviceName, uDeviceNo);

        ec = DzUnregisterService(wszDriverServiceName, pDevice->m_pDriverStub);
        if (FAILED(ec)) {
            ec = ELADRV_S_UNREGISTER_DRIVER_FAILED;
            goto Exit;
        }
    }

Exit:
    delete pDevice;

    return ec;
}

EXTERN void BuildDriverServiceName(
    wchar_t wszDriverServiceName[c_uMaxLengthOfDriverServiceName + 1],
    const wchar_t *wszDeviceName, uint_t uDeviceNo)
{
    wcscpy(wszDriverServiceName, DRIVER_SERVICE_PREFIX);
    wcscat(wszDriverServiceName, wszDeviceName);
    if (DEVICENO_NONE != uDeviceNo) {
        wchar_t wcsDeviceId[c_uMaxLengthOfUIntString + 1];
        UIntToWStr(wcsDeviceId, uDeviceNo);
        wcscat(wszDriverServiceName, wcsDeviceId);
    }
}

//
// local function definitions
//
STATIC const DriverConfig *FindDriverConfig(const wchar_t *wszDeviceName)
{
    assert(wszDeviceName);

    // TODO: optimize
    for (uint_t i = 0; i < dzcfg.uNumberOfDriverConfigs; i++) {
        if (0 ==
            wcscmp(wszDeviceName, dzcfg.pDriverConfigs[i].wszDeviceName)) {
            return &dzcfg.pDriverConfigs[i];
        }
    }
    return NULL;
}

// IMPORTANT: When own the s_deviceListLock
STATIC Device *FindDevice(const wchar_t *wszDeviceName, uint_t uDeviceNo)
{
    assert(wszDeviceName);

    // TODO: optimize
    Device *pDevice;
    ForEachDLinkNode(Device *, pDevice, &s_deviceListHead) {
        if (0 == wcscmp(wszDeviceName, pDevice->m_wszDeviceName)
            && uDeviceNo == pDevice->m_uDeviceNo) {
            return pDevice;
        }
    }
    return NULL;
}


STATIC void UIntToWStr(
    wchar_t wszBuffer[c_uMaxLengthOfUIntString + 1], uint_t uValue)
{
    assert(wszBuffer);

    if (0 == uValue) {
        wcscpy(wszBuffer, L"0");
    }
    else {
        uint_t i;
        for (i = 0; 0 < uValue; i++, uValue /= 10) {
            wszBuffer[i] = L'0' + uValue % 10;
        }
        wszBuffer[i] = L'\0';

        i--;
        for (uint_t j = 0; j < i; j++, i--) {
            Swap(wszBuffer[j], wszBuffer[i]);
        }
    }
}
